package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.*;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.tianji.promotion.enums.CouponStatus.*;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author author
 * @since 2025-02-09
 */
@Service
@RequiredArgsConstructor
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    private final ICouponScopeService couponScopeService;//优惠券的限定范围业务类
    private final IExchangeCodeService exchangeCodeService;//兑换码的业务类
    private  final IUserCouponService userCouponService;
    private final StringRedisTemplate redisTemplate;
    /**
     * 新增优惠券接口
     * @param dto
     */
    @Override
    public void saveCoupon(CouponFormDTO dto) {
        //1.dto转po 保存优惠券 coupon表
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        save(coupon);

        //2.判断是否限定了范围 dto.specific
        if (!dto.getSpecific()){
            return; //说明没有限定优惠券的使用返回
        }

        //3.如果dto.specific=true 需要校验dto.scopes
        List<Long> scopes = dto.getScopes();
        if (CollUtils.isEmpty(scopes)){
            throw new BadRequestException("分类id不能为空");
        }

        //4.保存优惠券限定范围 coupon_scope 批量
        List<CouponScope> clist = scopes
                .stream()
                .map(aLong -> new CouponScope().setCouponId(coupon.getId()).setBizId(aLong).setType(1))
                .collect(Collectors.toList());
        couponScopeService.saveBatch(clist);
    }


    /**
     * 分页查询优惠券接口
     * @param query
     * @return
     */
    @Override
    public PageDTO<CouponPageVO> queryCouponByPage(CouponQuery query) {
        // 1.分页查询
        Page<Coupon> page = this.lambdaQuery()
                .eq(query.getType() != null, Coupon::getDiscountType, query.getType())
                .eq(query.getStatus() != null, Coupon::getStatus, query.getStatus())
                .like(StringUtils.isNotBlank(query.getName()), Coupon::getName, query.getName())
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<Coupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        List<CouponPageVO> list = BeanUtils.copyList(records, CouponPageVO.class);
        // 3.返回
        return PageDTO.of(page, list);
    }


    /**
     * 发放优惠券接口
     * @param dto
     */
    @Override
    public void issueCoupon(CouponIssueFormDTO dto) {
        // 1.查询优惠券是否存在
        Coupon coupon = getById(dto.getId());
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在！");
        }

        //2.判断优惠券状态，暂停或待发放才可以发放
        if(coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != PAUSE){
            throw new BizIllegalException("优惠券状态错误！");
        }

        // 3.判断是否是立刻发放
        LocalDateTime issueBeginTime = dto.getIssueBeginTime();
        LocalDateTime now = LocalDateTime.now();
        boolean isBegin = issueBeginTime == null || !issueBeginTime.isAfter(now);

        //4.修改优惠券的 领取开始和结束日期 使用有效期开始和结束日 天数 状态
        // 4.1.拷贝属性到PO
        Coupon c = BeanUtils.copyBean(dto, Coupon.class);
        // 4.2.更新状态
        if (isBegin) {
            c.setStatus(ISSUING);
            c.setIssueBeginTime(now);
        }else{
            c.setStatus(UN_ISSUE);
        }
        // 4.3.写入数据库
        updateById(c);

        //5.如果优惠券是立刻发放，将优惠券信息（优惠券id，领取开始时间、结束时间、发行总数、限领数量） 采用hash存入redis
        if(isBegin){
            String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + coupon.getId();
//            redisTemplate.opsForHash().put(key,"issueBeginTime", String.valueOf(DateUtils.toEpochMilli(now)));
//            redisTemplate.opsForHash().put(key,"issueEndTime",String.valueOf(DateUtils.toEpochMilli(dto.getIssueEndTime())));
//            redisTemplate.opsForHash().put(key,"totalNum",String.valueOf(coupon.getTotalNum()));
//            redisTemplate.opsForHash().put(key,"userLimit",String.valueOf(coupon.getUserLimit()));

            Map<String, String> map = new HashMap<>(4);
            map.put("issueBeginTime", String.valueOf(DateUtils.toEpochMilli(now)));
            map.put("issueEndTime", String.valueOf(DateUtils.toEpochMilli(coupon.getIssueEndTime())));
            map.put("totalNum", String.valueOf(coupon.getTotalNum()));
            map.put("userLimit", String.valueOf(coupon.getUserLimit()));
            redisTemplate.opsForHash().putAll(key,map);
        }

        //6.判断是否需要生成兑换码，优惠券类型必须是兑换码，优惠券状态必须是待发放
        if(coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT){
            coupon.setIssueEndTime(c.getIssueEndTime());//兑换码的截止时间，就是优惠券领取的截止时间
            exchangeCodeService.asyncgenerateExchangeCode(coupon);//异步生成兑换码
        }
    }


    /**
     * 查询发放中的优惠券列表
     * @return
     */
    @Override
    public List<CouponVO> queryIssuingCoupons() {
        //1.查询db  coupon   条件：发放中，手动领取
        List<Coupon> couponList = this.lambdaQuery()
                .eq(Coupon::getStatus, ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();
        if (CollUtils.isEmpty(couponList)) {
            return CollUtils.emptyList();
        }

        //2.查询用户券表 user_coupon  条件：当前用户  发放中的优惠券id
        //正在发放中的优惠券集合
        Set<Long> couponIds = couponList.stream().map(Coupon::getId).collect(Collectors.toSet());
        List<UserCoupon> list = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, couponIds)
                .list();

        //2.1统计当前用户对优惠券的已经领取数量
        Map<Long, Long> issueMap = list.stream().collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        //key:优惠券id  值：已领数量

        //2.2统计当前用户对优惠券的已经领取并且未使用的数量
        Map<Long, Long> unusedMap  = list.stream()
                .filter(c->c.getStatus() == UserCouponStatus.UNUSED)
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));

        //2.po转vo返回
        List<CouponVO> voList = new ArrayList<>();
        for (Coupon c : couponList) {
            CouponVO vo = BeanUtils.copyBean(c, CouponVO.class);
            //优惠券还有剩余 （issue_num < total_num） 且 (统计用户券表user_coupon 取出当前用户已经数量 < user_limit)
            Long issueNum = issueMap.getOrDefault(c.getId(), 0L);
            boolean available = c.getIssueNum() < c.getTotalNum() && issueNum.intValue() < c.getUserLimit();
            vo.setAvailable(available); //是否可以领取

            //统计用户券表user_coupon取出当前用户已经且未使用的券数量
            boolean received = unusedMap.getOrDefault(c.getId(),0L) > 0;
            vo.setReceived(received); //是否可以使用
            voList.add(vo);
        }
        return voList;
    }
}
